/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @file db_session.c
 * pyhp session db backend
 */

#include <time.h>
#include <apr_strings.h>

#include "db_session.h"
#include "db.h"
#include "config.h"
#include "pyhp_layer.h"

static int pyhp_session_db_create_table_if_not_exists(PyHPData *pydata, PyObject *connection)
{
    return pyhp_cdb_exec(pydata, connection, "CREATE TABLE pyhp_sessions (id CHAR(36) PRIMARY KEY, content BLOB, date DATETIME)");
}

/**
 * Saves a session on the database
 * Creates the pyhp_sessions table if it does not already exist
 */
int pyhp_session_db_save(PyHPData *pydata, char const *id, char const *content)
{
    request_rec *r = pyhp_layer_get_internal(pydata, "apache_request");
    pyhp_config *conf = pyhp_get_config(r);
    PyObject *connection = pyhp_cdb_connect(pydata, conf->session_db_uri, conf->session_db_user,
                                            conf->session_db_pass);

    if (pyhp_session_db_create_table_if_not_exists(pydata, connection) == 0)
        pyhp_log(r, PyHP_INFO, "Missing pyhp session table, creating it...");

    char now[200];
    time_t t;
    struct tm *tmp;

    t = time(NULL);
    tmp = localtime(&t);
    strftime(now, sizeof(now), "%Y-%m-%d %H:%M:%S", tmp);

    char *sql = apr_pstrcat(r->pool, "DELETE FROM pyhp_sessions WHERE id='", id, "'", NULL);
    pyhp_cdb_exec(pydata, connection, sql);

    sql = apr_pstrcat(r->pool, "INSERT INTO pyhp_sessions VALUES('", id,  "',\"",  content, "\",'",  now, "')", NULL);
    pyhp_logf(r, PyHP_DEBUG, "Saving session on db using: %s", sql);
    return pyhp_cdb_exec(pydata, connection, sql);
}

void pyhp_session_db_wipe(PyHPData *pydata, char const *id)
{
    request_rec *r = pyhp_layer_get_internal(pydata, "apache_request");
    pyhp_config *conf = pyhp_get_config(r);
    PyObject *connection = pyhp_cdb_connect(pydata, conf->session_db_uri, conf->session_db_user,
                                            conf->session_db_pass);

    char *sql = apr_pstrcat(r->pool, "DELETE FROM pyhp_sessions WHERE id='", id, "'", NULL);
    pyhp_cdb_exec(pydata, connection, sql);
}

/**
 * Converts a python datetime object to a C string
 * it uses an internal static buffer so it isn't reentrant
 */
static char *pyhp_session_db_cstring_from_pydatetime(PyObject *datetime)
{
    static char buffer[128];
    char *res = NULL;

    PyObject *result = PyObject_CallMethod(datetime, "strftime", "s", "%Y-%m-%d %H:%M:%S");
    if ((result) && (result != Py_None)) {
        strcpy(buffer, PyString_AsString(result));
        res = buffer;
    }

    Py_XDECREF(result);
    return res;
}

/**
 * Checks if a session exists on db
 */
int pyhp_session_db_check_if_exists(PyHPData *pydata, char const *id)
{
    request_rec *r = pyhp_layer_get_internal(pydata, "apache_request");
    pyhp_config *conf = pyhp_get_config(r);
    PyObject *connection = pyhp_cdb_connect(pydata, conf->session_db_uri, conf->session_db_user,
                                            conf->session_db_pass);

    char *query = apr_pstrcat(r->pool, "SELECT * FROM pyhp_sessions WHERE id='", id, "'", NULL);

    PyObject *qresult = pyhp_cdb_query(pydata, connection, query);
    PyObject *lines = pyhp_cdb_result_fetchall(qresult);
    int rows_num = pyhp_cdb_result_rcount(qresult);

    pyhp_logf(r, PyHP_DEBUG, "Looking for session %s on database", id);

    if (rows_num > 0) {
        time_t now = time(NULL);
        struct tm session_time;

        PyObject *row = PyList_GetItem(lines, 0);
        PyObject *sess_date = PyDict_GetItemString(row, "date");
        char *sess_date_s = pyhp_session_db_cstring_from_pydatetime(sess_date);

        if ((!sess_date_s) || (!strptime(sess_date_s, "%Y-%m-%d %H:%M:%S", &session_time))) {
            char *sql = apr_pstrcat(r->pool, "DELETE FROM pyhp_sessions WHERE id='", id, "'", NULL);

            pyhp_logf(r, PyHP_WARN, "Session %s is invalid, deleting from database...", id);
            pyhp_cdb_exec(pydata, connection, sql);
            rows_num = 0;
        }
        else if ((mktime(&session_time) + 60*pyhp_config_sesstimeout(r)) < now) {
            char *sql = apr_pstrcat(r->pool, "DELETE FROM pyhp_sessions WHERE id='", id, "'", NULL);

            pyhp_logf(r, PyHP_DEBUG, "Session %s (%s) is expired, deleting from database...", id, sess_date_s);
            pyhp_cdb_exec(pydata, connection, sql);
            rows_num = 0;
        }
    }
    else {
        pyhp_logf(r, PyHP_WARN, "Session %s not found on database", id);
    }


    Py_XDECREF(lines);
    Py_XDECREF(qresult);
    rows_num = (rows_num <= 0) ? 0 : 1;
    return rows_num;
}

/**
 * Loads a pyhp session from the database.
 * The session MUST exists,  so check with pyhp_session_db_check_if_exists before
 * calling pyhp_session_db_load or it will crash.
 */
PyObject *pyhp_session_db_load(PyHPData *pydata, char const *id)
{
    request_rec *r = pyhp_layer_get_internal(pydata, "apache_request");
    pyhp_config *conf = pyhp_get_config(r);
    PyObject *connection = pyhp_cdb_connect(pydata, conf->session_db_uri, conf->session_db_user,
                                            conf->session_db_pass);

    char *query = apr_pstrcat(r->pool, "SELECT * FROM pyhp_sessions WHERE id='", id, "'", NULL);

    PyObject *qresult = pyhp_cdb_query(pydata, connection, query);
    PyObject *lines = pyhp_cdb_result_fetchall(qresult);

    PyObject *row = PyList_GetItem(lines, 0);
    PyObject *sess_content = PyDict_GetItemString(row, "content");
    PyObject *sess_data = pyhp_layer_unpack_object(pydata, PyString_AsString(sess_content));

    Py_XDECREF(lines);
    Py_XDECREF(qresult);

    return sess_data;
}


